package com.atguigu.tingshu.search.service.impl;


import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.*;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.NestedQuery;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.CompletionSuggestOption;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.Suggester;
import co.elastic.clients.elasticsearch.core.search.Suggestion;
import co.elastic.clients.json.JsonData;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.album.client.AlbumInfoFeignClient;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.model.album.BaseCategory3;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;


@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class SearchServiceImpl implements SearchService {

    @Qualifier("com.atguigu.tingshu.album.client.AlbumInfoFeignClient")
    @Autowired
    private AlbumInfoFeignClient albumInfoFeignClient;
    @Autowired
    private ElasticsearchClient elasticsearchClient;

    /**
     * 首页数据的展示
     *
     * @param c1Id 一级分类Id
     * @return
     */
    @Override
    @SneakyThrows
    public List<Map<String, Object>> channel(Long c1Id) {

        // 根据一级分类id 查询该一级分类下自定的7个三级分类
        Result<List<BaseCategory3>> BaseCategory3ListResult = albumInfoFeignClient.getC1Id7TopBaseCategory3List(c1Id);
        List<BaseCategory3> baseCategory3List = BaseCategory3ListResult.getData();
        Assert.notNull(baseCategory3List, "远程调用7个置顶的3级分类对象失败");

        // 将7个三级分类对象的id获取到然后转成FiledValue(未来es要用)
        List<FieldValue> fieldValueList = baseCategory3List.stream()
                .map(baseCategory3 ->
                        FieldValue.of(baseCategory3.getId()))
                .collect(Collectors.toList());

        //将7个三级分类对象的集合转成一个Map
        Map<Long, BaseCategory3> baseCategory3Map = baseCategory3List
                .stream()
                .collect(Collectors
                        .toMap(BaseCategory3::getId, baseCategory3 -> baseCategory3));

        // 查询es
        //      构建dsl语句
        SearchRequest request = buildChanelDsl(fieldValueList);

        //      发送查询请求
        SearchResponse<AlbumInfoIndex> response = elasticsearchClient
                .search(request, AlbumInfoIndex.class);

        //      解析数据
        return parseChanelResult(response, baseCategory3Map);
    }

    /**
     * 解析首页展示数据数据
     *
     * @param response         es返回的数据
     * @param baseCategory3Map 7个三级分类对象的集合
     * @return es数据
     */
    private List<Map<String, Object>> parseChanelResult(SearchResponse<AlbumInfoIndex> response, Map<Long, BaseCategory3> baseCategory3Map) {
        //根据response对象获取aggregations对象（父aggregations对象）
        Aggregate category3IdAggs = response.aggregations().get("category3IdAggs");

        //将Aggregate转成LongAggregate精准的聚合类型
        LongTermsAggregate lterms = category3IdAggs.lterms();

        ArrayList<Map<String, Object>> maps = new ArrayList<>();

        //从lterms获取桶
        List<LongTermsBucket> bucketList = lterms.buckets().array();
        for (LongTermsBucket bucket : bucketList) {
            long c3Id = bucket.key();
            HashMap<String, Object> map = new HashMap<>();
            map.put("baseCategory3", baseCategory3Map.get(c3Id));
            Aggregate albumScoreTopAggs = bucket.aggregations().get("albumScoreTopAggs");
            TopHitsAggregate topHitsAggregate = albumScoreTopAggs.topHits();
            List<Hit<JsonData>> hits = topHitsAggregate.hits().hits();
            ArrayList<AlbumInfoIndex> albumInfoIndicesList = new ArrayList<>();
            for (Hit<JsonData> hit : hits) {
                JsonData source = hit.source();
                AlbumInfoIndex albumInfoIndex = JSONObject
                        .parseObject(Objects.requireNonNull(source).toString(), AlbumInfoIndex.class);
                albumInfoIndicesList.add(albumInfoIndex);
            }
            map.put("list", albumInfoIndicesList);
            maps.add(map);
        }
        return maps;
    }

    /**
     * 构建首页展示数据dsl语句
     *
     * @param fieldValueList 7个三级分类对象的集合
     * @return es数据
     */
    private SearchRequest buildChanelDsl(List<FieldValue> fieldValueList) {
        SearchRequest searchRequest = SearchRequest.of(b -> b
                .index("albuminfo")
                .query(qb -> qb
                        .terms(tb -> tb
                                .field("category3Id")
                                .terms(tqfb -> tqfb
                                        .value(fieldValueList))))
                .aggregations("category3IdAggs", ab -> ab
                        .terms(tab -> tab
                                .field("category3Id")
                                .size(7))
                        .aggregations("albumScoreTopAggs", sab -> sab
                                .topHits(thab -> thab
                                        .size(6)
                                        .sort(sob -> sob
                                                .field(fsb -> fsb
                                                        .field("hotScore")
                                                        .order(SortOrder.Desc)))
                                ))
                )
        );
        System.out.println("searchRequest = " + searchRequest.toString());
        return searchRequest;
    }

    /**
     * 专辑搜索
     *
     * @param albumIndexQuery 专辑搜索条件
     * @return
     */
    @Override
    @SneakyThrows
    public AlbumSearchResponseVo search(AlbumIndexQuery albumIndexQuery) {
        //  构建dsl语句
        SearchRequest searchRequest = buildSearchConditionDsl(albumIndexQuery);

        //  发送请求
        SearchResponse<AlbumInfoIndex> response = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);

        //  解析数据
        AlbumSearchResponseVo albumSearchResponseVo = parseSearchResult(response);
        Long totalSize = albumSearchResponseVo.getTotal();
        albumSearchResponseVo.setPageNo(albumIndexQuery.getPageNo());
        albumSearchResponseVo.setPageSize(albumIndexQuery.getPageSize());
        long totalPages = (totalSize + albumSearchResponseVo.getPageSize() - 1) / albumSearchResponseVo.getPageSize();
        albumSearchResponseVo.setTotalPages(totalPages);

        //  返回结果
        return albumSearchResponseVo;
    }

    /**
     * 构建专辑搜索dsl语句
     *
     * @param albumIndexQuery 查询条件
     * @return 专辑搜索dsl语句
     */
    private SearchRequest buildSearchConditionDsl(AlbumIndexQuery albumIndexQuery) {
        //构建dsl语句的{}
        SearchRequest.Builder searchRequestBuilder = new SearchRequest.Builder();
        //构建BoolQueryBuilder对象
        BoolQuery.Builder boolQueryBuilder = new BoolQuery.Builder();

        // 判断查询条件

        // 判断关键字条件
        String keyword = albumIndexQuery.getKeyword();
        if (StringUtils.hasText(keyword)) {
            boolQueryBuilder.should(qb -> qb.match(mqb -> mqb.field("albumTitle").query(keyword)));
            boolQueryBuilder.should(qb -> qb.match(mqb -> mqb.field("albumIntro").query(keyword)));
            boolQueryBuilder.should(qb -> qb.match(mqb -> mqb.field("announcerName").query(keyword)));
        }

        // 判断分类条件
        Long category1Id = albumIndexQuery.getCategory1Id();
        if (category1Id != null) {
            boolQueryBuilder.must(qb -> qb.term(tqb -> tqb.field("category1Id").value(category1Id)));
        }
        Long category2Id = albumIndexQuery.getCategory2Id();
        if (category2Id != null) {
            boolQueryBuilder.must(qb -> qb.term(tqb -> tqb.field("category2Id").value(category2Id)));
        }
        Long category3Id = albumIndexQuery.getCategory3Id();
        if (category3Id != null) {
            boolQueryBuilder.must(qb -> qb.term(tqb -> tqb.field("category3Id").value(category3Id)));
        }

        // 判断标签条件["2:3", "3:6"]  属性id:属性值id
        List<String> attributeList = albumIndexQuery.getAttributeList();
        if (!CollectionUtils.isEmpty(attributeList)) {
            for (String attrIdAndValueId : attributeList) {
                String[] split = attrIdAndValueId.split(":");
                String attrId = split[0];
                String valueId = split[1];
                NestedQuery.Builder nestedBuilder = new NestedQuery.Builder();
                nestedBuilder.path("attributeValueIndexList")
                        .query(qb -> qb
                                .bool(bqb -> bqb
                                        .must(mqb -> mqb
                                                .term(tqb -> tqb
                                                        .field("attributeValueIndexList.attributeId")
                                                        .value(attrId)
                                                )
                                        )
                                        .must(mqb -> mqb
                                                .term(tqb -> tqb
                                                        .field("attributeValueIndexList.valueId")
                                                        .value(valueId))
                                        )
                                )
                        );
                // 得到NestedQuery对象
                NestedQuery nestedQuery = nestedBuilder.build();
                boolQueryBuilder.must(mq -> mq.nested(nestedQuery));
            }
        }

        //基础的Query查询对象构建
        BoolQuery boolQuery = boolQueryBuilder.build();
        searchRequestBuilder.index("albuminfo").query(qb -> qb.bool(boolQuery));

        //构建分页条件
        Integer pageNo = albumIndexQuery.getPageNo();
        Integer pageSize = albumIndexQuery.getPageSize();
        searchRequestBuilder.from((pageNo - 1) * pageSize).size(pageSize);

        //构建排序条件
        String order = albumIndexQuery.getOrder();
        if (StringUtils.hasText(order)) {
            String[] split = order.split(":");
            String dimension = split[0];
            String rule = split[1];
            SortOrder realRule = rule.equals("asc") ? SortOrder.Asc : SortOrder.Desc;
            switch (dimension) {
                case "1":
                    searchRequestBuilder.sort(sob -> sob.field(fob -> fob.field("hotScore").order(realRule)));
                    break;
                case "2":
                    searchRequestBuilder.sort(sob -> sob.field(fob -> fob.field("playStatNum").order(realRule)));
                    break;
                case "3":
                    searchRequestBuilder.sort(sob -> sob.field(fob -> fob.field("createTime").order(realRule)));
                    break;
            }
        } else {
            searchRequestBuilder.sort(sob -> sob.field(fob -> fob.field("hotScore").order(SortOrder.Desc)));
        }

        //构建高亮
        searchRequestBuilder.highlight(hb -> hb.fields("albumTitle", hfb -> hfb.preTags("<font style='color:red'>").postTags("</font>")));
        SearchRequest searchRequest = searchRequestBuilder.build();
        System.out.println(searchRequest);
        // 构建SearchRequest对象
        return searchRequest;
    }

    /**
     * 解析专辑搜索结果
     *
     * @param response es返回的数据
     * @return 专辑搜索结果
     */
    private AlbumSearchResponseVo parseSearchResult(SearchResponse<AlbumInfoIndex> response) {
        AlbumSearchResponseVo albumSearchResponseVo = new AlbumSearchResponseVo();
        long total = response.hits().total().value();
        ArrayList<AlbumInfoIndexVo> albumInfoIndexVos = new ArrayList<>();
        List<Hit<AlbumInfoIndex>> hits = response.hits().hits();
        for (Hit<AlbumInfoIndex> hit : hits) {
            AlbumInfoIndexVo albumInfoIndexVo = new AlbumInfoIndexVo();
            AlbumInfoIndex source = hit.source();
            BeanUtils.copyProperties(source, albumInfoIndexVo);
            //处理高亮
            Map<String, List<String>> highlight = hit.highlight();
            List<String> albumTitle = highlight.get("albumTitle");
            if (!CollectionUtils.isEmpty(albumTitle)) {
                String highlightAlbumTitle = albumTitle.get(0);
                albumInfoIndexVo.setAlbumTitle(highlightAlbumTitle);
            }
            albumInfoIndexVos.add(albumInfoIndexVo);
        }
        albumSearchResponseVo.setList(albumInfoIndexVos);
        albumSearchResponseVo.setTotal(total);
        return albumSearchResponseVo;
    }


    /**
     * 搜索建议
     *
     * @param content 搜索内容
     * @return
     */
    @SneakyThrows
    @Override
    public Set<String> completeSuggest(String content) {
        // 构建搜索建议的dsl
        SearchRequest searchRequest = buildSuggestDslk(content);
        // 执行搜索建议
        SearchResponse<SuggestIndex> response = elasticsearchClient.search(searchRequest, SuggestIndex.class);
        // 解析搜索建议结果
        HashSet<String> result =parseSuggestResult(response);
        if (result.size() < 10){
            SearchResponse<SuggestIndex> searchResponse = elasticsearchClient.search(sb -> sb
                    .index("suggestinfo").query(qb -> qb.
                            match(mqb -> mqb
                                    .field("title")
                                    .query(content))), SuggestIndex.class);
            for (Hit<SuggestIndex> hit : searchResponse.hits().hits()) {
                String title = hit.source().getTitle();
                result.add(title);
                if (result.size()>= 10){
                    break;
                }
            }
        }
        return result;
    }

    /**
     * 构建搜索建议的dsl
     * @param content 搜索内容
     * @return dsl
     */
    private SearchRequest buildSuggestDslk(String content) {
        Suggester.Builder suggestBuild = new Suggester.Builder();
        Suggester suggester = suggestBuild.suggesters("suggestionKeyword", fsb -> fsb.prefix(content).completion(csb -> csb.field("keyword").skipDuplicates(true)))
                .suggesters("suggestionKeywordSequence", fsb -> fsb.prefix(content).completion(csb -> csb.field("keywordSequence").skipDuplicates(true)))
                .suggesters("suggestionKeywordPinyin", fsb -> fsb.prefix(content).completion(csb -> csb.field("keywordPinyin").skipDuplicates(true)))
                .build();
        SearchRequest.Builder builder = new SearchRequest.Builder();
        SearchRequest searchRequest = builder.index("suggestinfo").suggest(suggester).build();
        return searchRequest;
    }

    /**
     * 解析搜索建议结果
     * @param response es返回的数据
     * @return 搜索建议结果
     */
    private HashSet<String> parseSuggestResult(SearchResponse<SuggestIndex> response) {
        Map<String, List<Suggestion<SuggestIndex>>> map = response.suggest();
        HashSet<String> hashSet = new HashSet<>();
        for (Map.Entry<String, List<Suggestion<SuggestIndex>>> stringListEntry : map.entrySet()) {
            List<Suggestion<SuggestIndex>> entryValue = stringListEntry.getValue();
            for (Suggestion<SuggestIndex> suggestIndexSuggestion : entryValue) {
                List<CompletionSuggestOption<SuggestIndex>> options = suggestIndexSuggestion.completion().options();
                for (CompletionSuggestOption<SuggestIndex> option : options) {
                    SuggestIndex suggestIndex = option.source();
                    String title = suggestIndex.getTitle();
                    hashSet.add(title);
                }
            }
        }
        return hashSet;
    }
}
